Skip to content

perf(pm): skip the npm --version spawn in npm projects - #3259

Merged
fengmk2 merged 4 commits into
voidzero-dev:mainfrom
magurotuna:perf/npm-version-from-package-json-v2
Oct 29, 2026
Merged

fengmk2 merged 4 commits into
voidzero-dev:mainfrom
magurotuna:perf/npm-version-from-package-json-v2

Conversation

@magurotuna

Copy link
Copy Markdown
Member

Summary

This PR saves about 110 ms on every vp run and every package-manager command (vp install, vp add, vp list, vp outdated, and others) in npm projects by making vp read the npm version from npm's package.json instead of running npm --version.

Benchmark

Command (npm project) Before After
vp run noop 225 ms 117 ms (−48%)
vp list 463 ms 346 ms (−25%)

noop is a script that runs node -e 0. Mean of 30 runs on:

  • CPU: AMD EPYC (Rome), 8 vCPUs (KVM virtual machine)
  • Memory: 16 GiB
  • OS: Debian 13 (Linux 6.12)
  • Node.js 24.21.0, npm 11.19.0

Approach

vp needs the npm version to build the right command line and the npm_config_user_agent for scripts. It ran npm --version for this, which starts Node.js and loads npm only to print the version field of npm's own package.json. Reading that file directly skips the process.

If the npm on PATH does not resolve into npm's package (for example, a version-manager shim, Corepack, or npm.cmd on Windows), vp still runs npm --version.

This PR only affects npm cases. pnpm and yarn leave room for a similar speedup: in system-first mode, vp runs pnpm --version or yarn --version, and the JavaScript builds (pnpm 11 and earlier, and yarn) answer slowly. However, pnpm and yarn can switch to the version that the project selects, so reading their package.json would not be correct. This is left for a follow-up.

In a project that uses npm, `vp run`, `vp list`, `vp outdated`,
`vp install`, and the other package-manager commands first ran
`npm --version` to learn the version of the npm on PATH. That starts Node.js
one more time for every command. Two paths ran it: the default resolution
(`resolve_npm_from_path`, which `vp run` also uses) and the system-first
shim mode (`system_package_manager` in the global CLI).

`npm --version` prints the `version` field of npm's own package.json. The npm
on PATH normally links into its package (`bin/npm` ->
`lib/node_modules/npm/bin/npm-cli.js`), so both paths read that manifest
now. If the nearest package.json within four parents does not belong to npm
(for example a version-manager shim, Corepack, or Windows `npm.cmd`), they
run `npm --version` as before.

The manifest belongs to the npm on PATH, not to the npm that ships with
Node.js, so an npm in a separate global prefix keeps its own version. npm does
not switch versions per project, so the value matches `npm --version`. pnpm,
yarn, and bun do not change: `vp` resolves their version itself and runs
`--version` only in system-first mode, and pnpm and yarn can switch to the
version that the project selects before they answer.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@magurotuna
magurotuna force-pushed the perf/npm-version-from-package-json-v2 branch from 68dbcc6 to fbc1817 Compare October 28, 2026 11:24
@fengmk2

fengmk2 commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

CI flaky issue has been fixed in main branch, you can merge commit from main. 😭

@liangmiQwQ

Copy link
Copy Markdown
Collaborator

This was a design left by me actually, I'll share some context:

npm --version call is designed to only happen for system-first mode. Because Vite+ doesn't take control of package manager, we cannot assume that the user's own package manager will be the declared version in package.json, the output npm --version then becomes the source of truth.

I originally designed a prove mechanism to throw error when the actual npm version is not the same as the declared version. However, I eventually dropped the mechanism because I think the actual version is known at that time and users would prefer for the command to execute successfully right away. (This part is open to discussion)

Also, the current vp install also calls npm --version even when in managed mode, it should be my mistakes. Fixing this is quite fine to me.

@magurotuna

Copy link
Copy Markdown
Member Author

@liangmiQwQ
Thanks for the context!

I don't think this PR changes which npm version vp uses in system-first mode. With this PR, vp reads the version field from npm's own package.json, not the user project's package.json. More concretely, it finds npm on PATH, resolves the symlink, and walks up the parent directories to npm's package.json. If it does not find npm's package.json there (for example, with a version-manager shim, Corepack, or npm.cmd on Windows), it falls back to npm --version.
In fact, the version field in npm's package.json is what npm --version prints as we can see in the npm implementation here:
https://github.com/npm/cli/blob/bfacd33ccbcd908480610703b60455d2da5b57a9/lib/npm.js#L12

So in short, this PR is to imitate what npm --version does, but without spawning a Node.js process, which takes about 110 ms per command.

@magurotuna
magurotuna marked this pull request as ready for review October 29, 2026 03:18
@fengmk2 fengmk2 added test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: create-e2e Run `vp create` e2e tests test: sfw labels Oct 29, 2026
@fengmk2

fengmk2 commented Oct 29, 2026

Copy link
Copy Markdown
Member

@magurotuna Let me take care of the flaky ci 😢

@fengmk2
fengmk2 merged commit 5d7a0f6 into voidzero-dev:main Oct 29, 2026
141 checks passed
@liangmiQwQ

Copy link
Copy Markdown
Collaborator

@liangmiQwQ

Thanks for the context!

I don't think this PR changes which npm version vp uses in system-first mode. With this PR, vp reads the version field from npm's own package.json, not the user project's package.json. More concretely, it finds npm on PATH, resolves the symlink, and walks up the parent directories to npm's package.json. If it does not find npm's package.json there (for example, with a version-manager shim, Corepack, or npm.cmd on Windows), it falls back to npm --version.

In fact, the version field in npm's package.json is what npm --version prints as we can see in the npm implementation here:

https://github.com/npm/cli/blob/bfacd33ccbcd908480610703b60455d2da5b57a9/lib/npm.js#L12

So in short, this PR is to imitate what npm --version does, but without spawning a Node.js process, which takes about 110 ms per command.

Oh! I previously thought you are reading projects' package.json, sorry for misunderstanding! ♥️

@fengmk2 fengmk2 mentioned this pull request Nov 3, 2026
fengmk2 added a commit that referenced this pull request Nov 4, 2026
Vite+ 1.5 adds an official Homebrew tap, runs TypeScript through Node.js
with `node --import vite-plus/register`, and lets `vp migrate` move
`oxnode` and transpile-only `ts-node` scripts to `vpx`.

### Features

- Install `vp` from the official Homebrew tap on macOS and glibc Linux
([docs](https://viteplus.dev/guide/homebrew))
([#2740](#2740)), by
@fengmk2:
  ```bash
brew tap voidzero-dev/vite-plus
https://github.com/voidzero-dev/vite-plus
  brew install voidzero-dev/vite-plus/vp
  ```
- `node --import vite-plus/register` runs TypeScript with the same
loader as `vpx`, so tools that start Node.js themselves, such as the
Node.js test runner, AVA, or a `NODE_OPTIONS` setting, can run
TypeScript files
([docs](https://viteplus.dev/guide/vpx#using-node-js-directly))
([#3208](#3208)), by
@fengmk2.
- `vp migrate` moves `oxnode` commands and transpile-only `ts-node`
commands to `vpx`, and moves their Node.js preloads and literal
`NODE_OPTIONS` values to `vite-plus/register`. `ts-node` commands that
type-check stay unchanged with a warning, and the `ts-node` dependency
is removed only when nothing still uses it. A project already on Vite+
gets this migration with `vp migrate --full`
([docs](https://viteplus.dev/guide/migrate-rules#oxnode-to-vpx))
([#3209](#3209),
[#3210](#3210)), by
@fengmk2.
- `vp migrate` warns when it finds Rollup, Rolldown, or Jest
dependencies with matching configuration, and explains how to move them
to Vite, tsdown, or Vitest. The warnings show during the first
migration, or with `vp migrate --full` in a project already on Vite+
([#3275](#3275)), by
@KTrain5169 and @fengmk2.
- `vite-plus` exports `resolveConfigFromFile(configFile, options?)`,
which resolves a config relative to the config file's directory instead
of `process.cwd()`, so editor plugins with several workspace folders
load the files of the right project
([#3249](#3249)), by
@leaysgur and @fengmk2.
- Security: the CLI ships `minimatch@11.0.4`, which fixes a denial of
service through glob pattern amplification
([GHSA-g3hv-75wg-m249](GHSA-g3hv-75wg-m249),
medium), and `zstd@0.13.4`, which fixes
[RUSTSEC-2026-0391](https://rustsec.org/advisories/RUSTSEC-2026-0391),
[RUSTSEC-2026-0398](https://rustsec.org/advisories/RUSTSEC-2026-0398),
and
[RUSTSEC-2026-0403](https://rustsec.org/advisories/RUSTSEC-2026-0403)
([#3328](#3328),
[#3331](#3331)), by
@renovate[bot] and @wan9chi.
- Upgrade the bundled tools
([#3205](#3205),
[#3236](#3236),
[#3261](#3261),
[#3206](#3206),
[#3330](#3330),
[#3338](#3338)), by
@voidzero-guard[bot] and @fengmk2:
- `rolldown@1.2.15` to `rolldown@1.2.16`, with
`experimentalInlineCommonChunks` in dev mode and no crash when the
working directory was deleted.
- `tsdown@0.23.1` to `tsdown@0.23.4`: `vp pack --env-file` can be
repeated to load several env files, the config has a JSON schema, and
`?inline` CSS imported from dependencies is inlined.
- `vitest@5.0.5` to `vitest@5.1.0`, with `--last-failed` and
`--since-last-run`, fake timers for `node:timers`, and interactive
review of failed snapshots. Browser mode now uses port 23315 by default.
- `oxlint@1.89.0` to `oxlint@1.91.0` and `oxlint-tsgolint@7.0.2004` to
`oxlint-tsgolint@7.0.2005`, with the new type-aware rule
`typescript/no-unsafe-enum-assignment`, wider checks in rules such as
`array-callback-return`, `no-useless-concat`, and `no-control-regex`,
and two rules moved into the `suspicious` category.
- `oxfmt@0.74.0` to `oxfmt@0.76.0`, with a new HTML formatter and
JavaScript and TypeScript fixes for parentheses, array layout, and line
width with wide characters.
- The new oxlint and oxfmt versions can flag code that passed before. If
your CI runs `vp check`, run `vp fmt` after upgrading.
- `vpx` and `vite-plus/register` run on `oxc-node@1.0.1`, which resolves
a `.jsx` specifier to its `.tsx` or `.ts` source.

### Fixes & Enhancements

- `vp create` into a target path that is a symbolic link removes only
the link after confirmation, instead of deleting the contents of the
linked directory, and the overwrite prompt names symbolic links and
files ([#2418](#2418)), by
@leslieeilsel and @fengmk2.
- `vp implode` removes only directories and shims that it can verify as
Vite+ installations, and refuses the home directory, its parents, and
filesystem roots even with `--yes`, so a `VP_HOME` that points at
another directory cannot delete unrelated files
([#3228](#3228)), by
@jong-kyung.
- `vp run` starts faster in workspaces whose tasks share cache `input`
and `output` patterns, because each pattern is parsed once. In a
138-package workspace this saves about 2 seconds of CPU on every run
([vite-task#887](voidzero-dev/vite-task#887),
[vite-task#890](voidzero-dev/vite-task#890)), by
@jasonkuhrt and @wan9chi.
- `vp run` and package-manager commands in npm projects start about 110
ms sooner, because `vp` reads the npm version from npm's `package.json`
instead of running `npm --version`
([#3259](#3259)), by
@magurotuna.
- `vp migrate` checks Vitest prerequisites faster in workspaces, because
it parses each lockfile once
([#3212](#3212)), by
@jong-kyung.
- The installer and `vp upgrade` install the global CLI's dependencies
with the Node.js version that the release pins (24.22.2 for this
release), instead of the latest LTS
([docs](https://viteplus.dev/guide/ci#node-js-during-installation))
([#3269](#3269)), by
@fengmk2.
- The Node.js `latest` alias in `vp env use`, `vp env default`, `vp env
pin`, `vp env install`, and `.nvmrc` resolves to the newest stable
release instead of a `27.0.0-alpha` build, and `vp env list-remote`
marks that stable release as latest
([#3335](#3335)), by
@voidzero-bot[bot] and @fengmk2.
- `VP_NODE_VERSION=22` and other partial versions work for project
commands such as `vp build`, `vp run`, and `vp list`, instead of failing
with a 404 download error
([#3341](#3341)), by
@jong-kyung.
- Two `vp install -g` commands that run at the same time no longer
delete each other's active package, and a command that starts while a
package manager is being installed no longer runs its shim before the
shim is executable
([#3185](#3185),
[#3248](#3248)), by
@jong-kyung.
- `vp config` waits and retries when another linked git worktree holds
the shared git config lock, so a `prepare` script no longer fails when
two worktrees install at the same time
([#3199](#3199)), by
@naokihaba.
- `vp env doctor` reports Node.js resolution failures, unreadable
config, a failing system `node`, and missing shims as errors and exits
with code 1. When it finds only warnings, the summary says `Some
warnings found` instead of `All checks passed`
([#3215](#3215),
[#3222](#3222)), by
@liangmiQwQ.
- Nushell completions for `vp` and `vpr` use the named completer input,
so Nushell 0.116 shows no deprecation warning, and they fall back to
file completion when fish is not installed. Run `vp env setup --refresh`
to regenerate `env.nu`
([#3267](#3267),
[#3276](#3276)), by
@naokihaba.
- `vp migrate` removes the `tsx` dependency even when a comment still
contains a `tsx` command or code fence, and keeps it for JSDoc type
imports that need it
([#3299](#3299)), by
@voidzero-bot[bot] and @fengmk2.
- The `prefer-vite-plus-imports` lint rule and its autofix leave the
`vite` and `vitest` imports that `vp migrate` keeps, such as `vitest`
imports in a package that lists `vitest` in `peerDependencies`, so `vp
lint --fix` no longer breaks the package for its plain Vitest users
([#3192](#3192)), by
@Dunqing.
- Generated Zed settings no longer force `typeAware` and
`unusedDisableDirectives`, so editor diagnostics follow the
`lint.options` in `vite.config.ts`. Existing editor settings stay
unchanged
([#2357](#2357)), by
@pweyck.
- `vp dlx` with npm runs the right executable for scoped packages such
as `@angular/cli`
([#3229](#3229)), by
@jong-kyung.
- `vp outdated -r` in npm projects checks the workspaces instead of
transitive dependencies, and requires npm 7.14.0 or later
([#3240](#3240)), by
@jong-kyung.
- Dependency filters such as `--prod` and `--dev` work with npm 6, and
filters that npm 6 cannot apply are rejected instead of passed through
([#3251](#3251)), by
@jong-kyung.
- Package-manager commands that the selected version does not have, such
as `vp pm stage` before npm 11.15.0, are skipped with a warning.
Unsupported `pnpm dlx` and `pnpm pack` options, and project-only flags
on managed global commands such as `vp install -g`, are rejected instead
of silently ignored
([#3286](#3286),
[#3285](#3285)), by
@jong-kyung.
- `vp toolchain -g` is short for `vp toolchain --global`
([#3189](#3189)), by
@jong-kyung.
- On Linux and macOS, programs in tasks with automatic file-access
tracking no longer abort when they call `exec` with an empty path or
`execl` with 32 or more arguments
([vite-task#896](voidzero-dev/vite-task#896),
[vite-task#865](voidzero-dev/vite-task#865)), by
@wan9chi and @tlq5l.

### Refactor

- Remove the test-only package download wrapper
([#3224](#3224)), by
@jong-kyung.

### Docs

- Explain that `vp check` stops at formatting failures, and that `vp
check` flags go before file paths
([#3262](#3262)), by
@SaKaNa-Y.
- Show how to align an existing `vite` dependency with its override
during a manual npm installation, to avoid `EOVERRIDE`
([#3157](#3157)), by
@SaKaNa-Y.
- Explain how environment variable prefixes such as `FOO=bar command`
work in task commands on every platform
([#3161](#3161)), by
@tartelettie.
- Restore the `/vp-setup` redirect and the redirects for moved guide
pages ([#3271](#3271)), by
@fengmk2.
- Align the labels of "See [...]" section links with their target titles
([#3231](#3231)), by
@yamaurakazuto.

### Chore

- Bump vite-task to `40cb9ca`; its user-facing changes are listed above
([#3246](#3246),
[#3280](#3280)), by
@wan9chi.
- Keep CI working on the Node.js 26 LTS and Ubuntu 24.04, cap job
runtimes, reduce Node.js downloads, avoid upgrade build and snapshot
download timeouts, pin Nushell, and restore Namespace Linux runners
([#3198](#3198),
[#3258](#3258),
[#3266](#3266),
[#3268](#3268),
[#3303](#3303),
[#3309](#3309),
[#3320](#3320),
[#3332](#3332)), by
@fengmk2 and @wan9chi.
- Add automated issue triage
([#3253](#3253),
[#3260](#3260)), by
@Boshen and @fengmk2.
- Run the upstream dependency upgrade with Kimi K3.1 Pro and report its
timeouts ([#3314](#3314),
[#3304](#3304)), by
@Brooooooklyn and @wan9chi.
- Stabilize and trim tests: rerun ESLint and Prettier migration fixtures
with `--full`, stabilize pnpm setup and Bun snapshots, update the tiptap
ecosystem pin, use a recorded package in the install shortcut case, and
remove duplicate `devEngines` parse tests
([#3193](#3193),
[#3196](#3196),
[#3242](#3242),
[#3305](#3305),
[#3301](#3301)), by
@liangmiQwQ, @fengmk2, @wan9chi, and @Brooooooklyn.
- Update Rust crates, synchronize workspace dependency requirements, and
stop installing `dprint` and `taplo` in `just init`
([#3234](#3234),
[#3310](#3310),
[#3250](#3250)), by
@renovate[bot], @leaysgur, and @fengmk2.
- Refine contributor guidance: the release-manager skill, isolated CLI
bootstrap checks in `AGENTS.md`, and the Rolldown exports in the CLI
bundling notes
([#3207](#3207),
[#3238](#3238),
[#3316](#3316)), by
@fengmk2 and @liangmiQwQ.

### Bundled Versions

| Tool | Version | Source | Changelog |
| --------------- | ---------- |
----------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| vite | `8.3.5` |
[`981f07f`](vitejs/vite@981f07f)
| unchanged |
| rolldown | `1.2.16` |
[`de6facf`](rolldown/rolldown@de6facf)
| [1.2.16](https://github.com/rolldown/rolldown/releases/tag/v1.2.16) |
| oxc-node | `1.0.1` |
[`7ff14c3`](oxc-project/oxc-node@7ff14c3)
| [1.0.1](https://github.com/oxc-project/oxc-node/releases/tag/v1.0.1) |
| tsdown | `0.23.4` | [npm](https://npmx.dev/package/tsdown/v/0.23.4) |
[0.23.2](https://github.com/rolldown/tsdown/releases/tag/v0.23.2),
[0.23.3](https://github.com/rolldown/tsdown/releases/tag/v0.23.3),
[0.23.4](https://github.com/rolldown/tsdown/releases/tag/v0.23.4) |
| vitest | `5.1.0` | [npm](https://npmx.dev/package/vitest/v/5.1.0) |
[5.1.0](https://github.com/vitest-dev/vitest/releases/tag/v5.1.0) |
| oxlint | `1.91.0` | [npm](https://npmx.dev/package/oxlint/v/1.91.0) |
[1.90.0](https://github.com/oxc-project/oxc/releases/tag/oxlint_v1.90.0),
[1.91.0](https://github.com/oxc-project/oxc/releases/tag/oxlint_v1.91.0)
|
| oxlint-tsgolint | `7.0.2005` |
[npm](https://npmx.dev/package/oxlint-tsgolint/v/7.0.2005) |
[7.0.2005](https://github.com/oxc-project/tsgolint/releases/tag/v7.0.2005)
|
| oxfmt | `0.76.0` | [npm](https://npmx.dev/package/oxfmt/v/0.76.0) |
[0.75.0](https://github.com/oxc-project/oxc/releases/tag/oxfmt_v0.75.0),
[0.76.0](https://github.com/oxc-project/oxc/releases/tag/oxfmt_v0.76.0)
|

### Upgrade

```bash
vp upgrade
```

### New Contributors

@yamaurakazuto, @tartelettie, @pweyck, @jasonkuhrt

**Full Changelog**:
v1.4.0...v1.5.0

---

Merging this PR will trigger the release workflow.

---------

Co-authored-by: voidzero-guard[bot] <278573678+voidzero-guard[bot]@users.noreply.github.com>
Co-authored-by: MK (fengmk2) <fengmk2@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test: create-e2e Run `vp create` e2e tests test: e2e Auto run e2e tests test: install-e2e run vite install e2e test test: sfw

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants